package org.eclipse.wst.server.preview.openejb.jetty.builder;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;

import org.apache.openejb.ClassLoaderUtil;
import org.apache.openejb.Injection;
import org.apache.openejb.assembler.classic.AppInfo;
import org.apache.openejb.assembler.classic.Assembler;
import org.apache.openejb.assembler.classic.InjectionBuilder;
import org.apache.openejb.assembler.classic.WebAppInfo;
import org.apache.openejb.config.AppModule;
import org.apache.openejb.config.ConfigurationFactory;
import org.apache.openejb.config.DeploymentLoader;
import org.apache.openejb.config.EjbModule;
import org.apache.openejb.config.ReadDescriptors;
import org.apache.openejb.config.UnknownModuleTypeException;
import org.apache.openejb.config.WebModule;
import org.apache.openejb.jee.WebApp;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import org.apache.openejb.util.URLs;
import org.apache.xbean.finder.ResourceFinder;
import org.apache.xbean.finder.UrlSet;
import org.mortbay.jetty.webapp.WebAppContext;

/**OpenejbWebappBinder : This class is responsible to search 
 * for EJB modules inside the web applications deployed inside 
 * the Jetty web server. All EJB classes found inside these 
 * web applications are bound into the JNDI name space of the 
 * OpenEJB container. 
 * 
 * @author Marcelo de Oliveira Dias
 */
public class OpenejbWebappBinder {
	
	private static final Logger logger = Logger.getInstance(LogCategory.OPENEJB.createChild("jetty"), "org.eclipse.wst.server.preview.openejb.jetty");
	
	/**Deploy in OpenEJB container the EJB modules encountered in the {@link WebAppContext}.
	 * The status of the standardContext need to be already started in Jetty.    
	 * 
	 * @param standardContext of the EJB module to be deployed.
	 */
	public void start( WebAppContext standardContext ){
		
		logger.info("Starting ejb deployment of " + standardContext.getContextPath() + " context path.");
		
		Assembler assembler = (Assembler) SystemInstance.get().getComponent(org.apache.openejb.spi.Assembler.class);
        if (assembler == null) {
            logger.error("OpenEJB has not been initialized so war will not be scanned for nested modules " + standardContext.getContextPath());
            return;
        }
        
        AppInfo appInfo = null;
        AppModule appModule = loadApplication(standardContext);
        if (appModule != null) {
            try {
            	ConfigurationFactory configurationFactory = new ConfigurationFactory();
                appInfo = configurationFactory.configureApplication(appModule);
                
                logger.debug("Creating Applictions");
                assembler.createApplication(appInfo, standardContext.getClassLoader());
  
                // todo add watched resources to context
            } catch (Exception e) {
                logger.error("Unable to deploy collapsed ear in war " + standardContext.getContextPath() + ": Exception: " + e.getMessage() );
            }
        }      
	}
	
	
	/**
	 * Creates a {@link WebModule} from a {@link WebAppContext}.
	 * It's created based on the <code>/WEB-INF/web.xml</code> of the webapp context.
	 * 
	 * @param standardContext the webapp context to be configured.
	 * @return WebModule created from the standardContext.
	 */
	private WebModule createWebModule( WebAppContext standardContext ){
		logger.debug("Creating WebModules");
		
		ServletContext servletContext = standardContext.getServletContext();
		
		WebApp webApp = new WebApp();
        try {
            URL webXmlUrl = servletContext.getResource("/WEB-INF/web.xml");
            if (webXmlUrl != null) {
                webApp = ReadDescriptors.readWebApp(webXmlUrl);
            }
        }catch( Exception e ){
        	logger.error("Unable to load web.xml in war " + standardContext.getContextPath() + ": Exception: " + e.getMessage(), e);
        }
		
        // create the web module
        String basePath = new File(servletContext.getRealPath(".")).getParentFile().getAbsolutePath();
        ClassLoader classLoader = ClassLoaderUtil.createTempClassLoader(standardContext.getClassLoader());
        String path = standardContext.getContextPath();
        
        WebModule webModule = new WebModule(webApp, path, classLoader, basePath, null);
        
		return webModule;
	}
	
	/**Create and load the {@link AppModule} from the {@link WebAppContext}.
	 * Each EJB module found in the webapp context is loaded, configured and put
	 * in the new AppModule created.
	 * 
	 * @param standardContext to be configured creating the AppModule.
	 * @return AppModule created.
	 */
	private AppModule loadApplication( WebAppContext standardContext ){
		
		WebModule webModule = createWebModule(standardContext);
		
		// create the app modules
        AppModule appModule = new AppModule(webModule.getClassLoader(), webModule.getJarLocation());

        // add the web module itself
        appModule.getWebModules().add(webModule);
        
     // check each url to determine if it is an ejb jar
        for (URL url : getUrls(standardContext)) {
            try {
                Class moduleType = DeploymentLoader.discoverModuleType(url, standardContext.getClassLoader(), true);
                if (EjbModule.class.isAssignableFrom(moduleType)) {
                    File file;
                    if (url.getProtocol().equals("jar")) {
                        url = new URL(url.getFile().replaceFirst("!.*$", ""));
                        file = URLs.toFile(url);
                    } else if (url.getProtocol().equals("file")) {
                        file = URLs.toFile(url);
                    } else {
                        logger.warning("Not loading " + moduleType.getSimpleName() + ".  Unknown protocol " + url.getProtocol());
                        continue;
                    }

                    logger.info("Found ejb module " + moduleType.getSimpleName() + " in war " + standardContext.getContextPath());

                    
                    // create the ejb module and set its moduleId to the webapp context root name
                    EjbModule ejbModule = new EjbModule(webModule.getClassLoader(), null,file.getAbsolutePath(), null, null);

                    // EJB deployment descriptors
                    try {
                        ResourceFinder ejbResourceFinder = new ResourceFinder("", standardContext.getClassLoader(), file.toURL());
                        Map<String, URL> descriptors = ejbResourceFinder.getResourcesMap("META-INF/");
                        ejbModule.getAltDDs().putAll(descriptors);
                    } catch (IOException e) {
                    	logger.error("Unable to determine descriptors in jar." + e);
                    }

                    // add module to app
                    appModule.getEjbModules().add(ejbModule);
                }
            } catch (IOException e) {
               logger.error("Unable to determine the module type of " + url.toExternalForm() + ": Exception: " + e.getMessage() );
            } catch (UnknownModuleTypeException ignore) {
            }

        }

        // Persistence Units via META-INF/persistence.xml
        try {
            ResourceFinder finder = new ResourceFinder("", standardContext.getClassLoader());
            List<URL> persistenceUrls = finder.findAll("META-INF/persistence.xml");
            
            appModule.getAltDDs().put("persistence.xml", persistenceUrls);
        } catch (IOException e) {
            logger.warning("Cannot load persistence-units from 'META-INF/persistence.xml' : " + e.getMessage());
        }

        return appModule;
	}
	
	
	/**Gets a list of urls from the a {@link WebAppContext}.
	 * This list will be searched to find the EJB modules of the WebAppContext.
	 * 
	 * @param standardContext.
	 * @return List<URL> of all urls in the standardContext to be searched.
	 */
	private List<URL> getUrls(WebAppContext standardContext) {
        List<URL> urls = null;
        try {
            ClassLoader classLoader = standardContext.getClassLoader();
            UrlSet urlSet = new UrlSet(classLoader);
            urlSet = urlSet.exclude(classLoader.getParent());
            urls = urlSet.getUrls();
        } catch (IOException e) {
            logger.warning("Unable to determine URLs in web application " + standardContext.getContextPath() );
        }
        return urls;
    }
}
